#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"

#include "llvm/IR/Constants.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Intrinsics.h"

#include "llvm/Assembly/AssemblyAnnotationWriter.h"
#include "llvm/Support/raw_ostream.h"

#include <list>

using namespace llvm;


struct bfLoop
{
	Value * beforeCell;
	PHINode * startCell;

	BasicBlock * beforeBlock;
	BasicBlock * startBlock;
};

class llvmBFCodeGen
{
public:
	static llvmBFCodeGen & getObj()
	{
		static llvmBFCodeGen self;
		return self;
	}

	/**
	 * Increment current cell code generation
	 * +         ++*h;
	 */
	void genAddFunc()
	{
		Value * curVal = this->builder->CreateLoad(this->curCell, _cellName);
		Value * res = this->builder->CreateAdd(curVal, ConstantInt::get(C, APInt(8, 1)), _cellName);
		this->builder->CreateStore(res, this->curCell);
	}

	/**
	 * Decrement current cell code generation
	 * -         --*h;
	 */
	void genSubFunc()
	{
		Value * curVal = this->builder->CreateLoad(this->curCell, _cellName);
		Value * res = this->builder->CreateSub(curVal, ConstantInt::get(C, APInt(8, 1)), _cellName);
		this->builder->CreateStore(res, this->curCell);
	}

	/**
	 * Goto next cell code generation
	 * >         ++h;
	 */
	void genNextFunc()
	{
		this->curCell = this->builder->CreateGEP(
				this->curCell, ConstantInt::get(C, APInt(8, 1)), _ptrName);
	}

	/**
	 * Goto prev cell code generation
	 * <         --h;
	 */
	void genPrevFunc()
	{
		this->curCell = this->builder->CreateGEP(
				this->curCell, ConstantInt::get(C, APInt(8, -1)), _ptrName);
	}

	/**
	 * Print symbol code generation
	 * .         putchar(*h);
	 */
	void genPrintFunc()
	{
		Value * cellVal = this->builder->CreateLoad(this->curCell, _cellName);
		Value * out = this->builder->CreateSExt(cellVal, IntegerType::getInt32Ty(C), _cellName);
		CallInst * putchar_call = this->builder->CreateCall(this->putcharFunc, out);
		putchar_call->setTailCall(false);
	}

	/**
	 * Read symbol code generation
	 * ,         *h=getchar();
	 */
	void genScanFunc()
	{
		CallInst * getchar_call = this->builder->CreateCall(this->getcharFunc, "_cellName");
		getchar_call->setTailCall(false);
		Value * cellVal = this->builder->CreateTrunc(
				getchar_call, IntegerType::getInt8Ty(C), "_resName");
		this->builder->CreateStore(cellVal, this->curCell);
	}

	/**
	 * Start loop code generation
	 * [         while(*h) {
	 * @bug this is do{...}while(), not while()
	 * @bug nested loops may crush llvm
	 */
	void genIzgnFunc()
	{
		// init loop
		bfLoop loop;
		loop.beforeBlock = this->block;
		loop.beforeCell = this->curCell;

		//load counter
		Value * counter = this->builder->CreateLoad(this->curCell, "counter");

		//create block
		loop.startBlock = BasicBlock::Create(C, "loopBeginBlock", this->mainFunc);

		this->builder->CreateBr(loop.startBlock);
		this->builder->SetInsertPoint(loop.startBlock);

		//phi node
		loop.startCell = builder->CreatePHI(IntegerType::getInt8Ty(C), 2, "it");
		loop.startCell->addIncoming(counter, loop.beforeBlock);

		//dec counter
		Value * delta = this->builder->CreateSub(loop.startCell, ConstantInt::get(C, APInt(8, 1)));
		delta->setName("delta");
		loop.startCell->addIncoming(delta, loop.startBlock);

		loops.push_back(loop);
		this->block = loop.startBlock;
	}

	/**
	 * End loop code generation
	 * ]         }
	 * @todo fixe APInt(8, 1)
	 */
	void genIzgbFunc()
	{
		bfLoop loop = this->loops.back();
		this->loops.pop_back();

		BasicBlock * nextBlock = BasicBlock::Create(C, "loopEndBlock", this->mainFunc);

		// if(counter ...
		//  APInt(8, 1) == kostil', need APInt(8, 0)
		Value* condition = this->builder->CreateICmpUGT(
				loop.startCell, ConstantInt::get(C, APInt(8, 1)), "cmp");
		this->builder->CreateCondBr(condition, loop.startBlock, nextBlock);
		this->builder->SetInsertPoint(nextBlock);
		this->block = nextBlock;
	}

	/**
	 * @todo setFoot!!
	 */
	void getCodeDump()
	{
		if(!this->footer)
		{
			this->setFoot();
		}
		this->module->dump();
	}

	/**
	 * write ir code to file
	 * @param const char * filename - output file name
	 * @todo setFoot!!, this->footer!!!
	 */
	void getCodeToFile(const char * filename)
	{
		if(!this->footer)
		{
			this->setFoot();
		}

		raw_ostream *out = &outs();
		std::string error;
		out = new raw_fd_ostream(filename, error);
		this->module->print(*out, new AssemblyAnnotationWriter);
	}

private:
	static llvmBFCodeGen * _self;
	const char * _cellName = "cell";
	const char * _ptrName = "ptr";
	const char * _resName = "res";
	//32Kb
	static const int _tapeSize = 65536;
	bool footer;
	IRBuilder<> * builder;
	Function * getcharFunc;
	Function * putcharFunc;
	Function * mainFunc;
	LLVMContext & C = getGlobalContext();
	Module * module;
	Value * curCell;
	Value * ptr_arr;
	std::list <bfLoop> loops;
	BasicBlock * block;


	llvmBFCodeGen()
	{
		this->initCode();
		this->footer = false;
	}

	/**
	 * Initial header code, declare functions allocate memory
	 */
	void initCode()
	{
		this->module = new Module("bf", C);

		this->getcharFunc = cast<Function>(
				this->module->getOrInsertFunction("getchar", IntegerType::getInt32Ty(C), NULL));

		this->putcharFunc = cast<Function>(
				this->module->getOrInsertFunction("putchar", IntegerType::getInt32Ty(C),
				IntegerType::getInt32Ty(C), NULL));

		Type * Tys[] = {Type::getInt8PtrTy(C), Type::getInt32Ty(C)};
		Function * memsetFunc = Intrinsic::getDeclaration(module, Intrinsic::memset, Tys);

		this->mainFunc = cast<Function>(
				this->module->getOrInsertFunction("main", Type::getVoidTy(C), NULL));

		this->block = BasicBlock::Create(C, "bf", this->mainFunc);
		this->builder = new IRBuilder<>(this->block );
		this->curCell = this->builder->CreateAlloca(IntegerType::getInt8Ty(C),
				ConstantInt::get(IntegerType::getInt32Ty(C), _tapeSize));

		//malloc
		ConstantInt * valMem = ConstantInt::get(C, APInt(32, _tapeSize));
		BasicBlock * BB = this->builder->GetInsertBlock();
		Type * IntPtrTy = IntegerType::getInt32Ty(C);
		Type * Int8Ty = IntegerType::getInt8Ty(C);
		//size of 1 cell
		Constant * allocsize = ConstantExpr::getSizeOf(IntegerType::getInt8Ty(C));
		allocsize = ConstantExpr::getTruncOrBitCast(allocsize, IntPtrTy);
		ptr_arr = CallInst::CreateMalloc(BB, IntPtrTy, Int8Ty, allocsize, valMem, NULL, "tape");
		BB->getInstList().push_back(cast<Instruction>(ptr_arr));

		//init tape
		Value * memset_params[] = {
			ptr_arr,
			ConstantInt::get(C, APInt(8, 0)),
			valMem,
			ConstantInt::get(C, APInt(32, 1)),
			ConstantInt::get(C, APInt(1, 0))
		};
		CallInst * memset_call = builder->CreateCall(memsetFunc, memset_params);
		memset_call->setTailCall(false);

		this->curCell = builder->CreateGEP(ptr_arr,
				ConstantInt::get(C, APInt(32, _tapeSize / 2)), "headPtr");

	}

	/**
	 * Main function return, free memory
	 */
	void setFoot()
	{
		this->footer = true;
		BasicBlock * endbb = this->builder->GetInsertBlock();
		endbb->getInstList().push_back(CallInst::CreateFree(ptr_arr, endbb));
		ReturnInst::Create(C, endbb);
	}

};
